package nl.ceasaro.playingcards;

/**
 * Represents a card in a typical deck of playing cards.
 * Card is immutable, and, therefore, thread safe.
 *
 * @author Garrett Smith, gsmith at northwestern dot edu, C.D. van Wieringen
 */
public final class Card implements Comparable
{

   // !!! preserve the order it is used to sort and determine which card is a higher or lower card
   // !!! the highest suit is first.
   public enum Suit
   {
      DIAMOND, CLUB, HEART, SPADE, NONE
   }

   public enum Color
   {
      BLACK, RED, NONE
   }

   // !!! preserve the order it is used to sort and determine which card is a higher or lower card
   // !!! the highest value is first.
   public enum Value
   {
      JOKER, ACE, KING, QUEEN, JACK, TEN, NINE, EIGHT, SEVEN, SIX, FIVE, FOUR, THREE, TWO
   }

   private final Suit _suit;
   private final Value _value;
   private final Color _color;
   private String imageExtension = "png";
   private int _hashCode;

   /**
    * Creates a joker.
    */
   public Card()
   {
      this(Value.JOKER, null);
   }

   /**
    * <p/>
    * Creates a new card with the specified suit and value.
    * </p>
    *
    * @param suit  the card's suit, for example, hearts
    * @param value the card's value, for example, a ten
    */
   public Card(final Value value, final Suit suit)
   {
      _value = value;
      if (Value.JOKER.equals(_value))
      {
         _suit = Suit.NONE;
         _color = Color.NONE;
         return;
      }
      else
      {
         _suit = suit;
      }
      if (Suit.HEART.equals(suit) || Suit.DIAMOND.equals(suit))
      {
         _color = Color.RED;
      }
      else if (Suit.CLUB.equals(suit) || Suit.SPADE.equals(suit))
      {
         _color = Color.BLACK;
      }
      else
      {
         _color = Color.NONE;
      }
      _hashCode = (_suit.hashCode() * 37 + 11) * _value.hashCode() + 11;
   }

   /**
    * @param card the card to check if it has a lower value.
    * @return true if the specified card is lower than this card, otherwise false is returned.
    */
   public boolean isLower(Card card)
   {
      return this.compareTo(card) < 0;
   }

   /**
    * @param card the card to check if it has a higher value.
    * @return true if the specified card is higher than this card, otherwise false is returned.
    */
   public boolean isHigher(Card card)
   {
      return this.compareTo(card) > 0;
   }

   /**
    * Returns whether the object referred to in the
    * <code>object</code> parameter is equal to this instance.
    *
    * @return whether the two cards are the same.
    */
   public boolean equals(final Object object)
   {
      if (object != null && object instanceof Card)
      {
         Card card = (Card) object;
         return (this.getSuit().equals(card.getSuit()) &&
                 this.getValue().equals(card.getValue()));
      }
      return false;
   }

   public int compareTo(Object o)
   {
      if (o instanceof Card)
      {
         Card otherCard = (Card) o;
         if (this.equals(otherCard))
         {
            return 0;
         }
         if (this.getValue().ordinal() > otherCard.getValue().ordinal())
         {
            return 1;
         }
         else if (this.getValue().ordinal() == otherCard.getValue().ordinal())
         {
            if (this.getSuit().ordinal() > otherCard.getSuit().ordinal())
            {
               return 1;
            }
            else
            {
               return -1;
            }
         }
         else
         {
            return -1;
         }
      }
      return -1;
   }

   /**
    * Returns a string representation of this card's color.
    *
    * @return a representation of this card's color.
    */
   public Color getColor()
   {
      return _color;
   }

   /**
    * Returns the card's suit.
    *
    * @return the card's suit.
    */
   public Suit getSuit()
   {
      return _suit;
   }

   /**
    * Returns the card's value.
    *
    * @return the card's value.
    */
   public Value getValue()
   {
      return _value;
   }

   /**
    * @return a string representing the image file name of this card
    *         e.g. S-3.png, H-A.png, D-J.png, H-7.png, etc
    */
   public String getImageFileName()
   {
      StringBuilder imageFileName = new StringBuilder();
      imageFileName.append(getImageName()).
              append(".").
              append(getImageExtension());
      return imageFileName.toString();
   }

   /**
    * @return the extension of the image representing this card. Default is 'png'.
    */
   public String getImageExtension()
   {
      return imageExtension;
   }

   /**
    * @param imageExtension the image extension e.g. png, jpg, gif, etc.
    */
   public void setImageExtension(String imageExtension)
   {
      this.imageExtension = imageExtension;
   }

   /**
    * determines the image name of this card.
    *
    * @return the image name e.g. S-3, H-A, D-J, H-7, etc.
    */
   public String getImageName()
   {
      StringBuilder imageName = new StringBuilder("");
      switch (getSuit())
      {
         case CLUB:
            imageName.append("C");
            break;
         case DIAMOND:
            imageName.append("D");
            break;
         case HEART:
            imageName.append("H");
            break;
         case SPADE:
            imageName.append("S");
            break;
         default:
            imageName.append("N");
            break;
      }
      imageName.append("-");
      switch (getValue())
      {
         case TWO:
            imageName.append("2");
            break;
         case THREE:
            imageName.append("3");
            break;
         case FOUR:
            imageName.append("4");
            break;
         case FIVE:
            imageName.append("5");
            break;
         case SIX:
            imageName.append("6");
            break;
         case SEVEN:
            imageName.append("7");
            break;
         case EIGHT:
            imageName.append("8");
            break;
         case NINE:
            imageName.append("9");
            break;
         case TEN:
            imageName.append("10");
            break;
         case JACK:
            imageName.append("J");
            break;
         case QUEEN:
            imageName.append("Q");
            break;
         case KING:
            imageName.append("K");
            break;
         case ACE:
            imageName.append("A");
            break;
         case JOKER:
            imageName.append("J");
            break;
      }
      return imageName.toString();
   }

   /**
    * Returns a hash value for this card.
    *
    * @return a hash value for this object.
    */
   public int hashCode()
   {
      return _hashCode;
   }


   /**
    * Returns a string representation this card's state.
    *
    * @return representing this card's state.
    */
   public String toString()
   {
      return "Card="
              + "[suit=" + _suit
              + ",value=" + _value
              + "]";
   }

   // A list of card contants to get a specific card quickly.
   public static final Card ACE_DIAMOND = new Card(Card.Value.ACE, Card.Suit.DIAMOND);
   public static final Card KING_DIAMOND = new Card(Card.Value.KING, Card.Suit.DIAMOND);
   public static final Card QUEEN_DIAMOND = new Card(Card.Value.QUEEN, Card.Suit.DIAMOND);
   public static final Card JACK_DIAMOND = new Card(Card.Value.JACK, Card.Suit.DIAMOND);
   public static final Card TEN_DIAMOND = new Card(Card.Value.TEN, Card.Suit.DIAMOND);
   public static final Card NINE_DIAMOND = new Card(Card.Value.NINE, Card.Suit.DIAMOND);
   public static final Card EIGHT_DIAMOND = new Card(Card.Value.EIGHT, Card.Suit.DIAMOND);
   public static final Card SEVEN_DIAMOND = new Card(Card.Value.SEVEN, Card.Suit.DIAMOND);
   public static final Card SIX_DIAMOND = new Card(Card.Value.SIX, Card.Suit.DIAMOND);
   public static final Card FIVE_DIAMOND = new Card(Card.Value.FIVE, Card.Suit.DIAMOND);
   public static final Card FOUR_DIAMOND = new Card(Card.Value.FOUR, Card.Suit.DIAMOND);
   public static final Card THREE_DIAMOND = new Card(Card.Value.THREE, Card.Suit.DIAMOND);
   public static final Card TWO_DIAMOND = new Card(Card.Value.TWO, Card.Suit.DIAMOND);

   public static final Card ACE_CLUB = new Card(Card.Value.ACE, Card.Suit.CLUB);
   public static final Card KING_CLUB = new Card(Card.Value.KING, Card.Suit.CLUB);
   public static final Card QUEEN_CLUB = new Card(Card.Value.QUEEN, Card.Suit.CLUB);
   public static final Card JACK_CLUB = new Card(Card.Value.JACK, Card.Suit.CLUB);
   public static final Card TEN_CLUB = new Card(Card.Value.TEN, Card.Suit.CLUB);
   public static final Card NINE_CLUB = new Card(Card.Value.NINE, Card.Suit.CLUB);
   public static final Card EIGHT_CLUB = new Card(Card.Value.EIGHT, Card.Suit.CLUB);
   public static final Card SEVEN_CLUB = new Card(Card.Value.SEVEN, Card.Suit.CLUB);
   public static final Card SIX_CLUB = new Card(Card.Value.SIX, Card.Suit.CLUB);
   public static final Card FIVE_CLUB = new Card(Card.Value.FIVE, Card.Suit.CLUB);
   public static final Card FOUR_CLUB = new Card(Card.Value.FOUR, Card.Suit.CLUB);
   public static final Card THREE_CLUB = new Card(Card.Value.THREE, Card.Suit.CLUB);
   public static final Card TWO_CLUB = new Card(Card.Value.TWO, Card.Suit.CLUB);

   public static final Card ACE_HEART = new Card(Card.Value.ACE, Card.Suit.HEART);
   public static final Card KING_HEART = new Card(Card.Value.KING, Card.Suit.HEART);
   public static final Card QUEEN_HEART = new Card(Card.Value.QUEEN, Card.Suit.HEART);
   public static final Card JACK_HEART = new Card(Card.Value.JACK, Card.Suit.HEART);
   public static final Card TEN_HEART = new Card(Card.Value.TEN, Card.Suit.HEART);
   public static final Card NINE_HEART = new Card(Card.Value.NINE, Card.Suit.HEART);
   public static final Card EIGHT_HEART = new Card(Card.Value.EIGHT, Card.Suit.HEART);
   public static final Card SEVEN_HEART = new Card(Card.Value.SEVEN, Card.Suit.HEART);
   public static final Card SIX_HEART = new Card(Card.Value.SIX, Card.Suit.HEART);
   public static final Card FIVE_HEART = new Card(Card.Value.FIVE, Card.Suit.HEART);
   public static final Card FOUR_HEART = new Card(Card.Value.FOUR, Card.Suit.HEART);
   public static final Card THREE_HEART = new Card(Card.Value.THREE, Card.Suit.HEART);
   public static final Card TWO_HEART = new Card(Card.Value.TWO, Card.Suit.HEART);

   public static final Card ACE_SPADE = new Card(Card.Value.ACE, Card.Suit.SPADE);
   public static final Card KING_SPADE = new Card(Card.Value.KING, Card.Suit.SPADE);
   public static final Card QUEEN_SPADE = new Card(Card.Value.QUEEN, Card.Suit.SPADE);
   public static final Card JACK_SPADE = new Card(Card.Value.JACK, Card.Suit.SPADE);
   public static final Card TEN_SPADE = new Card(Card.Value.TEN, Card.Suit.SPADE);
   public static final Card NINE_SPADE = new Card(Card.Value.NINE, Card.Suit.SPADE);
   public static final Card EIGHT_SPADE = new Card(Card.Value.EIGHT, Card.Suit.SPADE);
   public static final Card SEVEN_SPADE = new Card(Card.Value.SEVEN, Card.Suit.SPADE);
   public static final Card SIX_SPADE = new Card(Card.Value.SIX, Card.Suit.SPADE);
   public static final Card FIVE_SPADE = new Card(Card.Value.FIVE, Card.Suit.SPADE);
   public static final Card FOUR_SPADE = new Card(Card.Value.FOUR, Card.Suit.SPADE);
   public static final Card THREE_SPADE = new Card(Card.Value.THREE, Card.Suit.SPADE);
   public static final Card TWO_SPADE = new Card(Card.Value.TWO, Card.Suit.SPADE);

   public static final Card JOKER = new Card();

} // class Card
